home *** CD-ROM | disk | FTP | other *** search
/ NeXT Education Software Sampler 1992 Fall / NeXT Education Software Sampler 1992 Fall.iso / Programming / Source / nxyplot1.87 / Plot.m < prev    next >
Encoding:
Text File  |  1992-08-03  |  64.3 KB  |  2,247 lines

  1.  
  2. /* Generated by Interface Builder */
  3.  
  4. #import "defs.h"
  5. #import "Plot.h"
  6. #import <appkit/Button.h>
  7. #import <appkit/OpenPanel.h>
  8. #import <appkit/SavePanel.h>
  9. #import <appkit/Matrix.h>
  10. #import <appkit/Window.h>       // added by pdhowell
  11. #import <appkit/Cell.h>
  12. #import <appkit/appkit.h>
  13. #import <objc/Storage.h>
  14. #import <math.h>        /* for MAXFLOAT, etc. */
  15. #import <strings.h>
  16. #import <streams/streams.h>
  17. #import "ColumnSelectionHandler.h"
  18. #import "ErrorBarHandler.h"
  19. #import <defaults.h>
  20.  
  21. /* The following routines are in auxil.m: */
  22. extern void computeNiceLinInc(float *, float *, float *);
  23. extern void computeNiceLogInc(float *, float *, float *);
  24.  
  25. @implementation Plot
  26.  
  27. - makeSomeScrollWindows
  28. {
  29.   // the following lines exist only to initialize the ScrollWindows.....pdhowell
  30.   NXSize minsize = {0.0,0.0};
  31.   
  32.   [lineMatrixWindow   becomeScrollWindow];
  33.   [symbolMatrixWindow becomeScrollWindow];
  34.   [legendFormWindow   becomeScrollWindow];
  35.  
  36.   [lineMatrixWindow   setMinFrameSize:minsize];
  37.   [symbolMatrixWindow setMinFrameSize:minsize];
  38.   [legendFormWindow   setMinFrameSize:minsize];
  39.  
  40.   return self;
  41. }
  42.  
  43. + initialize
  44. {
  45.   const NXDefaultsVector nxyplotDefaults = {
  46.     { "colorOption", "NO"},
  47.     { "colorPrinting", "NO"},
  48.     { "cycleLineStyles", "NO"},
  49.     { "opaqueBackground", "YES"},
  50.     { NULL, NULL}
  51.   };
  52.  
  53.   self = [[Object alloc] init];
  54.   NXRegisterDefaults("nxyplot", nxyplotDefaults);
  55.   return self;
  56. }
  57.  
  58. - init
  59. {
  60.   // Initialize variables here:
  61.   nfilestotal = 0;
  62.   ncurvestotal = 0;
  63.   globaldatamin.x = MAXFLOAT;
  64.   globaldatamin.y = MAXFLOAT;
  65.   globaldatamax.x = -MAXFLOAT;
  66.   globaldatamax.y = -MAXFLOAT;
  67.   beepError = 0;
  68.   backgroundcolor = NX_COLORWHITE;
  69.   textcolor = NX_COLORBLACK;
  70.   srandom(10);            /* initialize for color selection  */
  71.   oldMin.x = 0.0;
  72.   oldMin.y = 0.0;
  73.   oldMax.x = 0.0;
  74.   oldMax.y = 0.0;
  75.   oldInc.x = 0.0;
  76.   oldInc.y = 0.0;
  77.   currentMin.x = 0.0;
  78.   currentMin.y = 0.0;
  79.   currentMax.x = 0.0;
  80.   currentMax.y = 0.0;
  81.   currentInc.x = 0.0;
  82.   currentInc.y = 0.0;
  83.  
  84.   return self;
  85. }
  86.  
  87. // Delete all data (free up the space that was malloc'ed)
  88. - removeAllFiles:sender
  89. {
  90.   int n, j;
  91.   datahunk *pdh;
  92.   const char * generictitle = "NXYPLOT";
  93.  
  94.   if (nfilestotal == 0) {
  95.     return self;
  96.   }
  97.  
  98.   // Put up an alert panel.  This method is called by a menu item and also
  99.   // by the removeAndOpen method; only if it's called by the menu item
  100.   // do we want to put up the alert panel.
  101.   if (sender != self) {
  102.     if (NXRunAlertPanel("Remove all", "Remove all files and clear plot",
  103.             "OK", "Cancel", NULL) == NX_ALERTALTERNATE) {
  104.       return self;
  105.     }
  106.   }
  107.  
  108.   for (n=nfilestotal-1; n>=0; n--) {
  109.     pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  110.     for (j = 0; j < pdh->ncurves; j++) {
  111.       free( (void *)*(pdh->y+j) );
  112.     }
  113.     free( (void *)(pdh->y) );
  114.     if (pdh->has_eybars) {
  115.       for (j = 0; j < pdh->ncurves; j++) {
  116.         free( (void *)*(pdh->ey+j) );
  117.       }
  118.       free( (void *)(pdh->ey));
  119.     }
  120.     free( (void *)(pdh->x) );
  121.     if (pdh->has_exbars) {
  122.       free( (void *)(pdh->ex));
  123.     }
  124.     free( (void *)(pdh->filename) );
  125.   }
  126.   [datahunkArray empty];
  127.   [self adjustPanels:ncurvestotal :-1]; /* -1 is a special signal */
  128.   nfilestotal = 0;
  129.  
  130.   [columnSelectionHandler removeAll:self];
  131.  
  132.   [errorBarHandler removeAll:self];
  133.  
  134.   [canvas display];        /* clear the canvas */
  135.  
  136.   [[canvas window] setTitle:generictitle];
  137.  
  138.   ncurvestotal = 0;
  139.  
  140.   // reset globaldatamin/max
  141.   globaldatamin.x = MAXFLOAT;
  142.   globaldatamin.y = MAXFLOAT;
  143.   globaldatamax.x = -MAXFLOAT;
  144.   globaldatamax.y = -MAXFLOAT;
  145.  
  146.   // clear xMin/Max/Inc and yMin/Max/Inc windows:
  147.   [xMin setStringValue:"" at:0];
  148.   [xMax setStringValue:"" at:0];
  149.   [xInc setStringValue:"" at:0];
  150.   [yMin setStringValue:"" at:0];
  151.   [yMax setStringValue:"" at:0];
  152.   [yInc setStringValue:"" at:0];
  153.   srandom(10);            /* initialize for color selection  */
  154.  
  155.   oldMin.x = 0.0;
  156.   oldMin.y = 0.0;
  157.   oldMax.x = 0.0;
  158.   oldMax.y = 0.0;
  159.   oldInc.x = 0.0;
  160.   oldInc.y = 0.0;
  161.   currentMin.x = 0.0;
  162.   currentMin.y = 0.0;
  163.   currentMax.x = 0.0;
  164.   currentMax.y = 0.0;
  165.   currentInc.x = 0.0;
  166.   currentInc.y = 0.0;
  167.  
  168.   return self;
  169. }
  170.  
  171. // Remove all existing files and open a new one
  172. - removeAndOpen:sender
  173. {
  174.   if (NXRunAlertPanel("New",
  175.               "Remove all files, clear plot\nand open new file",
  176.               "OK", "Cancel", NULL) == NX_ALERTALTERNATE) {
  177.     return self;
  178.   }
  179.  
  180.   [self removeAllFiles:self];
  181.   [self open:self];
  182.   return self;
  183. }
  184.  
  185.  
  186. - fixFileRemovalPanel:sender
  187. {
  188.   int n;
  189.   char title[80];
  190.   NXCoord dy;
  191.   int numrows, numcols;
  192.   NXSize cellsize, intercell;
  193.  
  194.   // Fix up the filename matrix
  195.   [fileRemovalMatrix getNumRows:&numrows numCols:&numcols];
  196.   [fileRemovalMatrix getCellSize:&cellsize];
  197.   [fileRemovalMatrix getIntercell:&intercell];
  198.  
  199.   [fileRemovalMatrix renewRows:nfilestotal cols:1];
  200.   dy = (NXCoord)(nfilestotal - numrows) * (cellsize.height + intercell.height);
  201.   for (n=0; n<nfilestotal; n++) {
  202.     if (!strncmp([self filename:(unsigned)n], "pasteboard", 10))
  203.       sprintf(title, "pasteboard");
  204.     else
  205.       sprintf(title, strrchr([self filename:(unsigned)n], '/') + 1);
  206.     [[fileRemovalMatrix cellAt:n :0] setStringValue:title];
  207.   }
  208.   [fileRemovalMatrix sizeToCells];
  209.   [fileRemovalMatrix moveBy:0.0 :-dy];
  210.  
  211.   // Fix up the buttons that go with the matrix of names
  212.   [fileRemovalButtons getCellSize:&cellsize];
  213.   [fileRemovalButtons getIntercell:&intercell];
  214.   dy = (NXCoord)(nfilestotal - numrows) * (cellsize.height + intercell.height);
  215.   [fileRemovalButtons renewRows:nfilestotal cols:1];
  216.   [fileRemovalButtons sizeToCells];
  217.   [fileRemovalButtons moveBy:0.0 :-dy];
  218.   for (n=0; n<nfilestotal; n++) {
  219.     [ [fileRemovalButtons cellAt:n :0] setState:0]; /* a safety play */
  220.   }
  221.  
  222.   [fileRemovalPanel display];
  223.  
  224.   if (sender != self) {
  225.     /* If this method is called from the menu, make the window key. */
  226.     [fileRemovalPanel makeKeyAndOrderFront:self];
  227.   }
  228.  
  229.   return self;
  230. }
  231.  
  232.  
  233. // Remove some existing files; the file removal is not hard, but correctly
  234. // updating the linestyle, symbolstyle, and legend matrices is harder.
  235. // Correctly handling the ColumnSelection and ErrorBar panels is even harder.
  236. - removeSomeFiles:sender
  237. {
  238.   int n, i, j;
  239.   int old_index = 0, current_index = 0;
  240.   /* These integers point to the columns of the linestyle and symbolstyle
  241.    * matrices as we run along updating the matrices.  They also serve in
  242.    * updating the curvecolors array.
  243.    */
  244.   datahunk *pdh;
  245.   int *newlinestyles, *newsymbolstyles;
  246.  
  247.   if (nfilestotal == 0) {
  248.     return self;
  249.   }
  250.  
  251.   /* Figure out what the linestyle and symbolstyle matrices should look
  252.    * like after the file removal process.  We do it this way because simply
  253.    * copying the columns of the matrices fails after the renewRows:cols:
  254.    * message is sent to them.
  255.    */
  256.   newlinestyles = (int *)malloc(ncurvestotal * sizeof(int));
  257.   newsymbolstyles = (int *)malloc(ncurvestotal * sizeof(int));
  258.   for (n=0; n < nfilestotal; n++) {
  259.     if ([[fileRemovalButtons cellAt:n :0] state] == 1) {
  260.       /* Just bump the index if the file is to be deleted.  */
  261.       old_index += [self nCurves:n];
  262.     }
  263.     else {
  264.       for (i=0; i < [self nCurves:n]; i++) {
  265.     for (j=0; j < N_LINE_STYLES; j++) {
  266.       if ([ [lineMatrix cellAt:j :old_index] state] == 1) {
  267.         newlinestyles[current_index] = j;
  268.         break;
  269.       }
  270.     }
  271.     for (j=0; j < N_SYMBOL_STYLES; j++) {
  272.       if ([ [symbolMatrix cellAt:j :old_index] state] == 1) {
  273.         newsymbolstyles[current_index] = j;
  274.         break;
  275.       }
  276.     }
  277.     /* Update the curvecolors array: */
  278.     curvecolors[current_index] = curvecolors[old_index];
  279.     old_index++;
  280.     current_index++;
  281.       }
  282.     }
  283.   }
  284.   /* We could do a realloc on curvecolors here, but not much would be saved. */
  285.  
  286.   /* Reset the running indices. */
  287.   old_index = 0;
  288.   current_index = 0;
  289.  
  290.   for (n=0; n < nfilestotal; n++) {
  291.     /* Is the nth file marked for deletion?  */
  292.     if ([[fileRemovalButtons cellAt:n :0] state] == 1) {    /* yes it is */
  293.       pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  294.       for (j = 0; j < pdh->ncurves; j++) {
  295.     free( (void *)*(pdh->y+j) );
  296.       }
  297.       free( (void *)(pdh->y) );
  298.       if (pdh->has_eybars) {
  299.     for (j = 0; j < pdh->ncurves; j++) {
  300.       free( (void *)*(pdh->ey+j) );
  301.     }
  302.     free( (void *)(pdh->ey));
  303.       }
  304.       free( (void *)(pdh->x) );
  305.       if (pdh->has_exbars) {
  306.     free( (void *)(pdh->ex));
  307.       }
  308.       free( (void *)(pdh->filename) );
  309.       old_index += [self nCurves:n];
  310.     }
  311.     else {
  312.       /* Column copying of the linestyle and symbolstyle matrices
  313.        * is handled later.  Do the legend form here.
  314.        */
  315.       for (j=0; j < [self nCurves:n]; j++) {
  316.     [legendForm setStringValue:[legendForm stringValueAt:old_index]
  317.                     at:current_index];
  318.     [legendForm drawCellAt:current_index];
  319.     old_index++;
  320.     current_index++;
  321.       }
  322.     }
  323.   }
  324.  
  325.   /* Get rid of extraneous legendForm entries: */
  326.   for (j=current_index; j<ncurvestotal; j++) {
  327.     [legendForm removeEntryAt:j];
  328.   }
  329.   [legendForm sizeToFit];
  330.   [ [legendForm window] display];
  331.  
  332.   /*
  333.    * Do the ColumnSelectionHandler manipulation here, before nfilestotal
  334.    * gets reset.
  335.    */
  336.   [columnSelectionHandler update:self];
  337.   [errorBarHandler update:self];
  338.  
  339.   /* We put the datahunkArray manipulation here because the removeAt method
  340.    * shifts the elements of the datahunkArray to close the gap created by
  341.    * removing one element; for this reason we count down rather than up.
  342.    */
  343.   j = nfilestotal;
  344.   for (n=nfilestotal-1; n >= 0; n--) {
  345.     if ([[fileRemovalButtons cellAt:n :0] state] == 1) {
  346.       [datahunkArray removeAt:(unsigned)n];
  347.       j--;
  348.     }
  349.   }
  350.   nfilestotal = j;
  351.   ncurvestotal = current_index;
  352.  
  353.   /* Now resize and display the linestyle and symbolstyle matrices */
  354.   [lineMatrix renewRows:N_LINE_STYLES cols:ncurvestotal];
  355.   [lineMatrix sizeToCells];
  356.   for (i=0; i<ncurvestotal; i++) {
  357.     for (j=0; j<N_LINE_STYLES; j++) {
  358.       [ [lineMatrix cellAt:j :i] setState:0];
  359.     }
  360.     [ [lineMatrix cellAt:newlinestyles[i] :i] setState:1];
  361.   }
  362.  
  363.   [lineText renewRows:1 cols:ncurvestotal];
  364.   [ [lineMatrix window] display];
  365.  
  366.   [symbolMatrix renewRows:N_SYMBOL_STYLES cols:ncurvestotal];
  367.   [symbolMatrix sizeToCells];
  368.   for (i=0; i<ncurvestotal; i++) {
  369.     for (j=0; j<N_SYMBOL_STYLES; j++) {
  370.       [ [symbolMatrix cellAt:j :i] setState:0];
  371.     }
  372.     [ [symbolMatrix cellAt:newsymbolstyles[i] :i] setState:1];
  373.   }
  374.   [symbolText renewRows:1 cols:ncurvestotal];
  375.   [ [symbolMatrix window] display];
  376.  
  377.  
  378.   /* must also fix up appearence of the file removal window */
  379.   [self fixFileRemovalPanel:self];
  380.   [fileRemovalPanel performClose:self];
  381.   free((void *)newlinestyles);
  382.   free((void *)newsymbolstyles);
  383.  
  384.   /* and replot automatically */
  385.   if (nfilestotal == 0) {
  386.     [canvas display];
  387.   }
  388.   else {
  389.     [self drawPlot:self];
  390.   }
  391.  
  392.   return self;
  393. }
  394.  
  395.  
  396. /* return the x data for the nth file */
  397. - (NXCoord *)xdata:(int)n
  398. {
  399.   datahunk *pdh;
  400.  
  401.   pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  402.   return pdh->x;
  403. }
  404.  
  405. /* return the y data for the nth file */
  406. - (NXCoord **)ydata:(int)n
  407. {
  408.   datahunk *pdh;
  409.  
  410.   pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  411.   return pdh->y;
  412. }
  413.  
  414. /* return the y error data for the nth file */
  415. - (NXCoord **)eydata:(int)n
  416. {
  417.   datahunk *pdh;
  418.  
  419.   pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  420.   return pdh->ey;
  421. }
  422.  
  423. /* return the x error data for the nth file */
  424. - (NXCoord *)exdata:(int)n
  425. {
  426.   datahunk *pdh;
  427.  
  428.   pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  429.   return pdh->ex;
  430. }
  431.  
  432. /* return the number of x-points in the nth file */
  433. - (int)nPoints:(int)n
  434. {
  435.   datahunk *pdh;
  436.  
  437.   pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  438.   return pdh->npoints;
  439. }
  440.  
  441. /* return the number of curves in the nth file */
  442. - (int)nCurves:(int)n
  443. {
  444.   datahunk *pdh;
  445.  
  446.   pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  447.   return pdh->ncurves;
  448. }
  449.  
  450. /* return the name of the nth file */
  451. - (char *)filename:(unsigned)n
  452. {
  453.   datahunk *pdh;
  454.  
  455.   pdh = (datahunk *)[datahunkArray elementAt:n];
  456.   return pdh->filename;
  457. }
  458.  
  459. /* Does the nth file have error bars in y? */
  460. - (BOOL) has_eybars:(int)n
  461. {
  462.   datahunk *pdh;
  463.  
  464.   pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  465.   return pdh->has_eybars;
  466. }
  467.  
  468. /* Does the nth file have error bars in x? */
  469. - (BOOL) has_exbars:(int)n
  470. {
  471.   datahunk *pdh;
  472.  
  473.   pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  474.   return pdh->has_exbars;
  475. }
  476.  
  477. - (int)nCurvesTotal     { return ncurvestotal;}
  478.  
  479. - (int)nFiles           { return nfilestotal;}
  480.  
  481. - makeLineStyle:(int)aCurve :(int)lineStyle
  482. {
  483.   int   row;
  484.  
  485.   for (row = 0; row < N_LINE_STYLES; row++)
  486.     [[lineMatrix cellAt:row :aCurve] setState:0]; /* turn off all */
  487.   [[lineMatrix cellAt:lineStyle :aCurve] setState:1];
  488.   return self;
  489. }
  490.  
  491. - makeSymbolType:(int)aCurve :(int)symType;
  492. {
  493.   int   row;
  494.  
  495.   for (row = 0; row < N_SYMBOL_STYLES; row++)
  496.     [[symbolMatrix cellAt:row :aCurve] setState:0]; /* turn off all */
  497.   [[symbolMatrix cellAt:symType :aCurve] setState:1];
  498.   return self;
  499. }
  500.  
  501. - (BOOL) xaxisLog
  502. {
  503.   if ( [xLinLog state] ) return YES;
  504.   else return NO;
  505. }
  506.  
  507. - forceXaxisLinear
  508. {
  509.   [xLinLog setState:0];
  510.   [xLinLog display];
  511.   return self;
  512. }
  513.  
  514. - forceXaxisLog
  515. {
  516.   [xLinLog setState:1];
  517.   [xLinLog display];
  518.   return self;
  519. }
  520.  
  521. - (BOOL) yaxisLog
  522. {
  523.   if ( [yLinLog state] ) return YES;
  524.   else return NO;
  525. }
  526.  
  527. - forceYaxisLinear
  528. {
  529.   [yLinLog setState:0];
  530.   [yLinLog display];
  531.   return self;
  532. }
  533.  
  534. - forceYaxisLog
  535. {
  536.   [yLinLog setState:1];
  537.   [yLinLog display];
  538.   return self;
  539. }
  540.  
  541. - (BOOL) shouldChangeLegendFont
  542. {
  543.   if ( [changeLegendFont state] ) return YES;
  544.   else return NO;
  545. }
  546.  
  547. - (BOOL) shouldChangeLegendTitleFont
  548. {
  549.   if ( [changeLegendTitleFont state] ) return YES;
  550.   else return NO;
  551. }
  552.  
  553. - (BOOL) shouldChangeMainTitleFont
  554. {
  555.   if ( [changeMainTitleFont state] ) return YES;
  556.   else return NO;
  557. }
  558.  
  559. - (BOOL) shouldChangeYTitleFont
  560. {
  561.   if ( [changeYTitleFont state] ) return YES;
  562.   else return NO;
  563. }
  564.  
  565. - (BOOL) shouldChangeXTitleFont
  566. {
  567.   if ( [changeXTitleFont state] ) return YES;
  568.   else return NO;
  569. }
  570.  
  571. - (BOOL) shouldChangeTicLabelFont
  572. {
  573.   if ( [changeTicLabelFont state] ) return YES;
  574.   else return NO;
  575. }
  576.  
  577. - (int)providelinestyle:(int)aCurve
  578. {
  579.   int   row, cellstate;
  580.  
  581.   /* First, if line is turned off, return that */
  582.   if ([ [lineMatrix cellAt:N_LINE_STYLES-1 :aCurve] state] == 1) {
  583.     return N_LINE_STYLES-1;
  584.   }
  585.   /*
  586.    * Next, check if we are printing or previewing and if we should
  587.    * cycle the line styles
  588.    */
  589.   if ( ( ([printPreview state] == 1) || (NXDrawingStatus == NX_PRINTING))
  590.       && ([accPrintLineStyleButton state] == 1) ) {
  591.     return aCurve % (N_LINE_STYLES - 1);
  592.   }
  593.   /*
  594.    * Here we can just look at the linestyle matrix.
  595.    */
  596.   else {
  597.     for (row = 0; row < N_LINE_STYLES; row++) {
  598.       cellstate = [ [lineMatrix cellAt:row :aCurve] state];
  599.       if (cellstate == 1) return row;
  600.     }
  601.   }
  602.   return 0;            /* for safety */
  603. }
  604.  
  605. - (int)providesymbolstyle:(int)aCurve
  606. {
  607.   int   row, cellstate;
  608.  
  609.   for (row = 0; row < N_SYMBOL_STYLES; row++) {
  610.     cellstate = [ [symbolMatrix cellAt:row :aCurve] state];
  611.     if (cellstate == 1) return row;
  612.   }
  613.   return 0;            /* for safety */
  614. }
  615.  
  616. /*
  617.  * Changed floats to doubles in the following group.  With floats, when
  618.  * the value was 250.4, the value returned by floatValueAt: was
  619.  * 250.399994 (for example).  (This appears to be a problem with the
  620.  * atof function on Unix 32-bit systems.)  This gave troubles in the
  621.  * tic mark routine in PlotView.m.  
  622.  */
  623. - (double)provideXmin  {return [xMin doubleValueAt:0];}
  624. - (double)provideXmax  {return [xMax doubleValueAt:0];}
  625. - (double)provideXinc  {return [xInc doubleValueAt:0];}
  626. - (double)provideYmin  {return [yMin doubleValueAt:0];}
  627. - (double)provideYmax  {return [yMax doubleValueAt:0];}
  628. - (double)provideYinc  {return [yInc doubleValueAt:0];}
  629.  
  630. - resetXmin:(double)aNum { [xMin setDoubleValue:aNum at:0]; return self; }
  631. - resetXmax:(double)aNum { [xMax setDoubleValue:aNum at:0]; return self; }
  632. - resetXinc:(double)aNum { [xInc setDoubleValue:aNum at:0]; return self; }
  633. - resetYmin:(double)aNum { [yMin setDoubleValue:aNum at:0]; return self; }
  634. - resetYmax:(double)aNum { [yMax setDoubleValue:aNum at:0]; return self; }
  635. - resetYinc:(double)aNum { [yInc setDoubleValue:aNum at:0]; return self; }
  636.  
  637. - (float)provideGlobalXmin {return globaldatamin.x;}
  638. - (float)provideGlobalYmin {return globaldatamin.y;}
  639.  
  640. - resetMinMax:sender
  641. {
  642.   int n;
  643.   datahunk * pdh;
  644.  
  645.   // We have to go through all the data and recalculate min/max since
  646.   // some data curves may have been "turned off" (by setting their linestyle
  647.   // to none and symbolstyle to none).
  648.  
  649.   for (n=0; n<nfilestotal; n++) {
  650.     pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  651.     [self findMinMax:pdh];
  652.   }
  653.   [self findGlobalMinMax];
  654.  
  655.   [self niceMinMaxInc];
  656.   [self drawPlot:self];        /* redraw plot automatically */
  657.   return self;
  658. }
  659.  
  660. - drawPlotButton:(int)state
  661. {
  662.   if (state==0) {
  663.     [plotButton highlight:NO];    /* will display normal title */
  664.   }
  665.   if (state==1) {
  666.     [plotButton highlight:YES];    /* will display alternate title */
  667.   }
  668.   return self;
  669. }
  670.  
  671. // We make the plotParam object responsible for checking parameters
  672. // before the PlotView object is called.  Thus the PlotView object can
  673. // assume the parameters are OK, and it doesn't have to do any checking.
  674. // Things to check: xinc has the same sign as xmax-xmin (same for y);
  675. // no negative data if log plot requested on x or y axis; there won't be
  676. // too many tic marks requested.
  677. - sanityCheck
  678. {
  679.   float xinc = [self provideXinc];
  680.   float xmax = [self provideXmax], xmin = [self provideXmin];
  681.   float yinc = [self provideYinc];
  682.   float ymax = [self provideYmax], ymin = [self provideYmin];
  683.   int   nticmarks;
  684.  
  685.   /* First check: no nonpositive data if logarithmic axis */
  686.   /* Also check that increment is > 5 (need rint(log10(increment)) >=1 ) */
  687.   if ( [self xaxisLog] ) {
  688.     if (globaldatamin.x <= 0.0 || xmin <= 0.0 || xmax <= 0.0) {
  689.       [xLinLog setState:0];    /* back to linear */
  690.       NXBeep();            /* audible alert */
  691.       beepError = 1;
  692.     }
  693.     if (xinc < 5.0) {
  694.       [self resetXinc:(double)10.0];
  695.       NXBeep();
  696.       beepError = 12;
  697.     }
  698.   }
  699.   if ( [self yaxisLog] ) {
  700.     if (globaldatamin.y <= 0.0 || ymin <= 0.0 || ymax <= 0.0) {
  701.       [yLinLog setState:0];    /* back to linear */
  702.       NXBeep();            /* audible alert */
  703.       beepError = 2;
  704.     }
  705.     if (yinc < 5.0) {
  706.       [self resetYinc:(double)10.0];
  707.       NXBeep();
  708.       beepError = 12;
  709.     }
  710.   }
  711.   /* Second check: xinc has same sign as xmax and xmin */
  712.   if (xinc*(xmax-xmin) <= 0.0) { /* the bad case - avoid infinite loop */
  713.     if (xinc < 0.0) {        /*     in PlotView:drawSelf           */
  714.       xinc = -xinc;
  715.       [self resetXinc:xinc];
  716.       NXBeep();
  717.       beepError = 3;
  718.     }
  719.     if (xmax <= xmin) {
  720.       [self niceMinMaxInc];
  721.       NXBeep();            /* alert */
  722.       beepError = 4;
  723.     }
  724.   }
  725.   /* And similarly for yinc */
  726.   if (yinc*(ymax-ymin) <= 0.0) { /* the bad case - avoid infinite loop */
  727.     if (yinc < 0.0) {
  728.       yinc = -yinc;
  729.       [self resetYinc:yinc];
  730.       NXBeep();            /* alert */
  731.       beepError = 5;
  732.     }
  733.     if (ymax <= ymin) {
  734.       [self niceMinMaxInc];
  735.       NXBeep();            /* alert */
  736.       beepError = 6;
  737.     }
  738.   }
  739.   /* Third check: no more than 100 (say) tic marks on either axis */
  740.   if ( ![self xaxisLog] ) {    /*  linear axis */
  741.     nticmarks = (int) ((xmax - xmin) / xinc) ;
  742.     if (nticmarks > 100) {
  743.       computeNiceLinInc(&xmin, &xmax, &xinc);
  744.       [self resetXmin:xmin];
  745.       [self resetXmax:xmax];
  746.       [self resetXinc:xinc];
  747.       NXBeep();            /* alert */
  748.       beepError = 7;
  749.     }
  750.   }
  751.   if ( ![self yaxisLog] ) {    /*  linear axis */
  752.     nticmarks = (int) ((ymax - ymin) / yinc) ;
  753.     if (nticmarks > 100) {
  754.       computeNiceLinInc(&ymin, &ymax, &yinc);
  755.       [self resetYmin:ymin];
  756.       [self resetYmax:ymax];
  757.       [self resetYinc:yinc];
  758.       NXBeep();            /* alert */
  759.       beepError = 8;
  760.     }
  761.   }
  762.  
  763.   return self;
  764. }
  765.  
  766. - drawPlot:sender
  767. {
  768.   char c_xmin[20], c_xmax[20], c_ymin[20], c_ymax[20], c_xinc[20], c_yinc[20];
  769.   float xmin, xmax, ymin, ymax, xinc, yinc;
  770.  
  771.   if (nfilestotal==0) return self;
  772.  
  773.   [self drawPlotButton:1];    /* display "Plotting" */
  774.   [self sanityCheck];        /* disallow various bad parameters */
  775.  
  776.   /* maybe save min/max/inc */
  777.   /* This really gets crufty: when we look at the TextField xMin and
  778.    * get its float value via [xMin floatValueAt:0], we get
  779.    * a float which is the result of applying atof() to a string.  The
  780.    * float which results may not be the same as the float that was originally
  781.    * written into the TextField.  So we apply the process ourselves:
  782.    * float --> string (via sprintf) --> float (via atof).  Ugly, but necessary
  783.    * (otherwise, if you zoom, then hit the Plot button twice, then hit
  784.    * the Previous View button, you might not get back to where you want).
  785.    */
  786.   sprintf(c_xmin,"%f",currentMin.x);
  787.   sprintf(c_xmax,"%f",currentMax.x);
  788.   sprintf(c_xinc,"%f",currentInc.x);
  789.   sprintf(c_ymin,"%f",currentMin.y);
  790.   sprintf(c_ymax,"%f",currentMax.y);
  791.   sprintf(c_yinc,"%f",currentInc.y);
  792.   xmin = (float)atof(c_xmin);
  793.   xmax = (float)atof(c_xmax);
  794.   xinc = (float)atof(c_xinc);
  795.   ymin = (float)atof(c_ymin);
  796.   ymax = (float)atof(c_ymax);
  797.   yinc = (float)atof(c_yinc);
  798.   if ( [xMin floatValueAt:0] != xmin
  799.       || [xMax floatValueAt:0] != xmax
  800.       || [xInc floatValueAt:0] != xinc
  801.       || [yMin floatValueAt:0] != ymin
  802.       || [yMax floatValueAt:0] != ymax
  803.       || [yInc floatValueAt:0] != yinc ) {
  804.     oldMin.x = currentMin.x;
  805.     currentMin.x = [xMin floatValueAt:0];
  806.     oldMax.x = currentMax.x;
  807.     currentMax.x = [xMax floatValueAt:0];
  808.     oldInc.x = currentInc.x;
  809.     currentInc.x = [xInc floatValueAt:0];
  810.     oldMin.y = currentMin.y;
  811.     currentMin.y = [yMin floatValueAt:0];
  812.     oldMax.y = currentMax.y;
  813.     currentMax.y = [yMax floatValueAt:0];
  814.     oldInc.y = currentInc.y;
  815.     currentInc.y = [yInc floatValueAt:0];
  816.   }
  817.  
  818.   [canvas display];
  819.   [self drawPlotButton:0];    /* display "Plot" */
  820.   return self;
  821. }
  822.  
  823. // Allocate enough memory and read the data points
  824. /*
  825.  * This code makes the following assumptions:
  826.  * 1. Any data on a line following the character "!" is to be discarded.
  827.  * 2. We can determine the number of curves by looking at the first
  828.  * line of data, which should be of the form
  829.  *  x  y1  y2    ...    yn
  830.  * (possibly separated by commas, with possible trailing comment).
  831.  * 3. Other lines of the file may contain arbitrary text, but contain no
  832.  * numerals or periods (these get interpreted as floating point numbers
  833.  * when the file is scanned); also, anything after a "!" is discarded.
  834.  *
  835.  * It is not easy to make a completely general and bullet-proof scanning
  836.  * routine.  This code is fairly robust and was easy to write.
  837.  */
  838. - (int) readData:(NXStream *)aDataStream :(char *)fname
  839. {
  840.   BOOL    inword = NO;
  841.   char    c;
  842.   int     j, size = ALLOCSIZE;
  843.   int     tmpint = 0;        /* tmpint initialized to avoid compiler warning */
  844.   int     oldncurves = ncurvestotal;
  845.   datahunk *pdh = (void *)NULL;
  846.   BOOL    noxdata = NO;
  847.   float   tmpfloat = 0.0;    /* initialized to avoid compiler warning */
  848.   
  849.   [self preludeToReading:fname :&pdh];    /* take care of some housekeeping */
  850.   
  851.   /* Figure out the number of curves in the file by reading characters  */
  852.   /* until a newline is encountered; the number of curves is one less   */
  853.   /* than the number of words found.                                    */
  854.   /* We assume the input file is an ascii file; a compressed file will  */
  855.   /* have been pumped through zcat and written to a temporary file.     */
  856.   pdh->ncurves = -1;
  857. top_of_file: ;
  858.   while (1) {
  859.     c = (char)NXGetc(aDataStream);
  860.     if (c == '\n' || c == (char)EOF) {
  861.       break;            /* breaks out of while loop */
  862.     }
  863.     if (c == '!') {        /* comment signal: start discarding characters */
  864.       while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /*  do nothing */
  865.       if (pdh->ncurves == -1)    /* any data found yet? */
  866.     goto top_of_file;        /* ugh */
  867.       else
  868.     break;
  869.     }
  870.     else if ((inword==NO) && !(c==' ' || c=='\t')) {
  871.       pdh->ncurves++;
  872.       inword = YES;
  873.     }
  874.     else if ((inword==YES) && (c==' ' || c=='\t')) {
  875.       inword = NO;
  876.     }
  877.   }
  878.   if (pdh->ncurves == -1) {    /* couldn't find "\n", give up (after cleanup) */
  879.     [plotButton setAltTitle:"Plotting"]; /* reset plot button */
  880.     [plotButton highlight:NO];
  881.     NXPing();                       /* force redraw */
  882.     free( (void *)(pdh->filename) );
  883.     nfilestotal--;
  884.     return 0;
  885.   }
  886.   if (pdh->ncurves == 0) {    /* only one column, assume x data are integers */
  887.     noxdata = YES;
  888.     tmpfloat = 1.0;
  889.     pdh->ncurves = 1;
  890.     if (pdh->has_exbars || pdh->has_eybars) {
  891.       NXRunAlertPanel("Read Data (with error bars)",
  892.               "Error bars expected, only one curve found\n"
  893.               "Unsetting error bar button and continuing",
  894.               "OK", NULL, NULL);
  895.       [errorBars setTitle:"No error bars"];
  896.       pdh->has_exbars = NO;
  897.       pdh->has_eybars = NO;
  898.     }
  899.   }
  900.  
  901.   /*
  902.    * We read more than one column; if there are error bars we must adjust ncurves.
  903.    *    case            pdh->ncurves        true no. of curves
  904.    *   y only            2n                    n
  905.    *   x only            n (>=2)               n-1
  906.    *   y and x          2n+1 (>=3)             n
  907.    */
  908.   if (pdh->has_eybars && !pdh->has_exbars) {
  909.     if ( pdh->ncurves % 2  !=  0 ) {
  910.       NXRunAlertPanel("Read Data (with error bars)",
  911.               "Strange number of curves found\n"
  912.               "Unsetting error bar button and continuing",
  913.               "OK", NULL, NULL);
  914.       [errorBars setTitle:"No error bars"];
  915.       pdh->has_eybars = NO;
  916.     }
  917.     else {
  918.       pdh->ncurves = pdh->ncurves / 2;
  919.     }
  920.   }
  921.   else if (pdh->has_exbars && !pdh->has_eybars) {
  922.     if ( pdh->ncurves < 2 ) {
  923.       NXRunAlertPanel("Read Data (with error bars)",
  924.               "Too few curves found\n"
  925.               "Unsetting error bar button and continuing",
  926.               "OK", NULL, NULL);
  927.       [errorBars setTitle:"No error bars"];
  928.       pdh->has_exbars = NO;
  929.     }
  930.     else {
  931.       pdh->ncurves--;
  932.     }
  933.   }
  934.   else if (pdh->has_exbars && pdh->has_eybars) {
  935.     if ( pdh->ncurves < 3  ||  pdh->ncurves % 2 == 0 ) {
  936.       NXRunAlertPanel("Read Data (with error bars)",
  937.               "Bad number of curves found\n"
  938.               "Unsetting error bar button and continuing",
  939.               "OK", NULL, NULL);
  940.       [errorBars setTitle:"No error bars"];
  941.       pdh->has_exbars = NO;
  942.       pdh->has_eybars = NO;
  943.     }
  944.     else {
  945.       pdh->ncurves = (pdh->ncurves - 1) / 2;
  946.     }
  947.   }
  948.  
  949.   /* Now read the data into memory */
  950.   NXSeek(aDataStream, 0L, NX_FROMSTART);
  951.     
  952.   pdh->x = (NXCoord *)malloc( size * sizeof(NXCoord) );
  953.   if (pdh->has_exbars) {
  954.     pdh->ex = (NXCoord *)malloc( size * sizeof(NXCoord) );
  955.   }
  956.   pdh->y = (NXCoord **)malloc( pdh->ncurves * sizeof(NXCoord *) );
  957.   for (j = 0; j < pdh->ncurves; j++) {
  958.     *(pdh->y+j) = (NXCoord *)malloc( size * sizeof(NXCoord) );
  959.   }
  960.   if (pdh->has_eybars) {
  961.     pdh->ey = (NXCoord **)malloc( pdh->ncurves * sizeof(NXCoord *) );
  962.     for (j = 0; j < pdh->ncurves; j++) {
  963.       *(pdh->ey+j) = (NXCoord *)malloc( size * sizeof(NXCoord) );
  964.     }
  965.   }
  966.   pdh->npoints = 0;
  967.   while(1) {
  968.     if (noxdata) {
  969.       *(pdh->x+pdh->npoints) = tmpfloat++;
  970.     }
  971.     else {
  972.       if (pdh->has_exbars) {    /* x error bars, read two items*/
  973.     while ( (tmpint=NXScanf(aDataStream, "%f", pdh->x+pdh->npoints)) == 0 ) {
  974.       if (c == '!') {
  975.         while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
  976.         goto skipline;    /* ugh */
  977.       }
  978.     }
  979.     while ( (tmpint=NXScanf(aDataStream, "%f", pdh->ex+pdh->npoints)) == 0 ) {
  980.       if (c == '!') {
  981.         while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
  982.         goto skipline;    /* ugh */
  983.       }
  984.     }
  985.       }
  986.       else {            /* no x error bars, read one item */
  987.     while ( (tmpint=NXScanf(aDataStream, "%f", pdh->x+pdh->npoints)) == 0 ) {
  988.       c = (char)NXGetc(aDataStream);    /* throw away extraneous characters */
  989.       if (c == '!') {
  990.         while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
  991.         goto skipline;    /* ugh */
  992.       }
  993.     }
  994.       }
  995.       if (tmpint == EOF) break;    /* break out of the while(1) loop */
  996.     }
  997.  
  998.     if (pdh->has_eybars) {    /* y error bars, read two items */
  999.       for (j = 0; j < 2 * pdh->ncurves; j++) {
  1000.     if (j%2 == 0) {        /* j is even, reading y value */
  1001.       while( (tmpint=NXScanf(aDataStream, "%f", *(pdh->y+(j/2))+ pdh->npoints)) == 0 ) {
  1002.         c = (char)NXGetc(aDataStream);    /* throw away the next character */
  1003.         if (c == '!') {        /* comment signal; start discarding characters */
  1004.           while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
  1005.           goto skipline;    /* ugh */
  1006.         }
  1007.       }
  1008.     }
  1009.     else {            /* j is odd, reading error */
  1010.       while( (tmpint=NXScanf(aDataStream, "%f", *(pdh->ey+(j-1)/2)+ pdh->npoints)) == 0 ) {
  1011.         c = (char)NXGetc(aDataStream);
  1012.         if (c == '!') {
  1013.           while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ;
  1014.           goto skipline;
  1015.         }
  1016.       }
  1017.     }
  1018.       }
  1019.     }
  1020.     else {            /* no error bars, read one item */
  1021.       for (j = 0; j < pdh->ncurves; j++) {
  1022. /*
  1023.  * Try to allow extraneous characters here (if scanf returns 0, which means
  1024.  * it didn't find a number, just do a getc on the input stream to throw that
  1025.  * character away.  This will allow commas and alphabetic characters in the
  1026.  * middle of an input line (no digits or periods, though!).
  1027.  */
  1028.     while( (tmpint=NXScanf(aDataStream, "%f", *(pdh->y+j)+ pdh->npoints)) == 0 ) {
  1029.       c = (char)NXGetc(aDataStream);    /* throw away the next character */
  1030.       if (c == '!') {        /* comment signal; start discarding characters */
  1031.         while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
  1032.         goto skipline;    /* ugh */
  1033.       }
  1034.     }
  1035.       }
  1036.     }
  1037.     if (tmpint == EOF) break;    /* could get this if noxdata==YES */
  1038.     pdh->npoints++;
  1039.     if (pdh->npoints == size) {        /* get more memory */
  1040.       size += ALLOCSIZE;
  1041.       pdh->x = (NXCoord *)realloc(pdh->x, size * sizeof(NXCoord));
  1042.       if (pdh->has_exbars) {
  1043.     pdh->ex = (NXCoord *) realloc(pdh->ex, size * sizeof(NXCoord));
  1044.       }
  1045.       for (j = 0; j < pdh->ncurves; j++) {
  1046.     *(pdh->y+j) = (NXCoord *)realloc( *(pdh->y+j), size * sizeof(NXCoord) );
  1047.       }
  1048.       if (pdh->has_eybars) {
  1049.     for (j = 0; j < pdh->ncurves; j++) {
  1050.       *(pdh->ey+j) = (NXCoord *)realloc( *(pdh->ey+j), size * sizeof(NXCoord) );
  1051.     }
  1052.       }
  1053.     }
  1054. skipline: ;
  1055.   }
  1056.  
  1057.   [self postludeToReading:fname :oldncurves :pdh];
  1058.  
  1059.   ncurvestotal += pdh->ncurves;
  1060.  
  1061.   return pdh->npoints;
  1062. }
  1063.  
  1064. /* Might want to make sure INLINE_MATH is defined when math.h is included
  1065.  * (for guaranteed fast logarithms?)
  1066.  */
  1067. - checkLinLog:(datahunk *)pdh
  1068. {
  1069.   /* Check x and y axes -- do a heuristic test for log/lin.
  1070.    * The test employed comes from M. Merriam and xyplot.
  1071.    */
  1072.   double    scale, linsum, logsum;
  1073.   register  double tmp;
  1074.   int       i, j;
  1075.  
  1076.   /* First test x axis.  */
  1077.   if (pdh->datamax.x > 0.0  &&  pdh->datamin.x > 0.0
  1078.       && pdh->datamax.x != pdh->datamin.x) {
  1079.     scale = fabs( (double)pdh->datamax.x - (double)pdh->datamin.x );
  1080.     linsum = 0.0;
  1081.     for (j=1; j<pdh->npoints; j++) {
  1082.       tmp = ( (double)pdh->x[j]-(double)pdh->x[j-1] )/(double)scale;
  1083.       linsum += tmp*tmp;
  1084.     }
  1085.     scale = log10( (double)pdh->datamax.x/(double)pdh->datamin.x );
  1086.     /* what if datamax.x<datamin.x? */
  1087.     logsum = 0.0;
  1088.     for (i=1; i<pdh->npoints; i++) {
  1089.       tmp = log10( (double)pdh->x[i]/(double)pdh->x[i-1] ) / scale;
  1090.       logsum += tmp*tmp;
  1091.     }
  1092.     if (linsum < logsum) {
  1093.       pdh->xaxislin = YES;    /* linear axis */
  1094.     }
  1095.     else {
  1096.       pdh->xaxislin = NO;    /* logarithmic axis */
  1097.     }
  1098.   }
  1099.   else {
  1100.     pdh->xaxislin = YES;    /* linear */
  1101.   }
  1102.   /* Now test y axis */
  1103.   if (pdh->datamax.y > 0.0  &&  pdh->datamin.y > 0.0
  1104.       && pdh->datamax.y != pdh->datamin.y) {
  1105.     scale = fabs( (double)pdh->datamax.y - (double)pdh->datamin.y );
  1106.     linsum = 0.0;
  1107.     for (j=0; j<pdh->ncurves; j++) {
  1108.       for (i=1; i<pdh->npoints; i++) {
  1109.     tmp = ( (double)*(*(pdh->y+j)+i) - (double)*(*(pdh->y+j)+i-1) )
  1110.       / scale;        /* avoid overflow */
  1111.     linsum += tmp*tmp;
  1112.       }
  1113.     }
  1114.     scale = log10((double)pdh->datamax.y/(double)pdh->datamin.y);
  1115.     logsum = 0.0;
  1116.     for (j=0; j<pdh->ncurves; j++) {
  1117.       for (i=1; i<pdh->npoints;i++) {
  1118.     tmp = log10( (double)*(*(pdh->y+j)+i)/(double)*(*(pdh->y+j)+i-1) ) / scale;
  1119.     logsum += tmp*tmp;
  1120.       }
  1121.     }
  1122.     if (linsum < logsum) {
  1123.       pdh->yaxislin = YES;    /* linear axis */
  1124.     }
  1125.     else {
  1126.       pdh->yaxislin = NO;    /* logarithmic axis */
  1127.     }
  1128.   }
  1129.   else {
  1130.     pdh->yaxislin = YES;    /* linear */
  1131.   }
  1132.   return self;
  1133. }
  1134.  
  1135. // This routine works as follows:
  1136. // If these is just one file, we set the x and y axes to linear or logarithmic
  1137. // depending on our heuristic.
  1138. // If there is more than one file, we don't change the axes unless there
  1139. // would be an illegal result (trying to plot a negative number on a
  1140. // logarithmic axis).
  1141. - checkGlobalLinLog
  1142. {
  1143.   datahunk *pdh;
  1144.  
  1145.   if (nfilestotal == 1) {
  1146.     pdh = (datahunk *)[datahunkArray elementAt:0];
  1147.     if ( pdh->xaxislin )
  1148.       [xLinLog setState:0]; /* linear */
  1149.     else
  1150.       [xLinLog setState:1]; /* logarithmic */ 
  1151.     if ( pdh->yaxislin )
  1152.       [yLinLog setState:0]; /* linear */
  1153.     else
  1154.       [yLinLog setState:1]; /* logarithmic */
  1155.   }
  1156.   else {
  1157.     if ( [self xaxisLog] && globaldatamin.x <= 0.0) {
  1158.       [xLinLog setState:0];    /* back to linear */
  1159.       NXBeep();            /* audible alert */
  1160.       beepError = 9;
  1161.     }
  1162.     if ( [self yaxisLog] && globaldatamin.y <= 0.0) {
  1163.       [yLinLog setState:0];    /* back to linear */
  1164.       NXBeep();            /* audible alert */
  1165.       beepError = 10;
  1166.     }
  1167.   }
  1168.   [xLinLog display];
  1169.   [yLinLog display];
  1170.  
  1171.   return self;
  1172. }
  1173.  
  1174. - adjustLineStyleMatrix:(int)column :(int)row
  1175. {
  1176.   /* Adjust the given column of the lineMatrix object, turning off all
  1177.    * buttons except for the given row.
  1178.    */
  1179.   int i;
  1180.  
  1181.   for (i = 0; i < N_LINE_STYLES; i++) {
  1182.     [ [lineMatrix cellAt:i :column] setState:0];
  1183.   }
  1184.   [ [lineMatrix cellAt:row :column] setState:1];  
  1185.  
  1186.   return self;
  1187. }
  1188.  
  1189. - redisplayLineStyleMatrix
  1190. {
  1191.   [lineMatrix display];
  1192.   return self;
  1193. }
  1194.  
  1195. - adjustSymbolTypeMatrix:(int)column :(int)row
  1196. {
  1197.   /* Adjust the given column of the symbolMatrix object, turning off all
  1198.    * buttons except for the given row.
  1199.    */
  1200.   int i;
  1201.  
  1202.   for (i = 0; i < N_SYMBOL_STYLES; i++) {
  1203.     [ [symbolMatrix cellAt:i :column] setState:0];
  1204.   }
  1205.   [ [symbolMatrix cellAt:row :column] setState:1];  
  1206.   return self;
  1207. }
  1208.  
  1209. - redisplaySymbolTypeMatrix
  1210. {
  1211.   [symbolMatrix display];
  1212.   return self;
  1213. }
  1214.  
  1215. - adjustPanels:(int)oldn :(int)newn
  1216. {
  1217.   /*
  1218.    * Resize the linestyle matrix, the symbolstyle matrix, and
  1219.    * the legend form.
  1220.    * Also arrange to select and highlight only the top button in
  1221.    * each column of the linestyle and symbolstyle matrices.
  1222.    * This code could stand to be cleaned up.
  1223.    */
  1224.   char formtitle[80];
  1225.   int j, k;
  1226. // some additions on by pdhowell for ScrollWindow compatibility
  1227.   NXRect legendFormFrame, lineMatrixFrame, symbolMatrixFrame;
  1228.   datahunk *pdh;
  1229.  
  1230.   if (newn == -1) {        /* flag to delete all and start over */
  1231.     for (j=oldn-1; j>=1; j--) {
  1232.       [lineText removeColAt:j andFree:YES];
  1233.       [symbolText removeColAt:j andFree:YES];
  1234.       [lineMatrix removeColAt:j andFree:YES];
  1235.       [symbolMatrix removeColAt:j andFree:YES];
  1236.       [legendForm removeEntryAt:j];
  1237.     }
  1238.     for (j=0; j<1; j++) {
  1239.       if ( [ [lineMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
  1240.     [ [lineMatrix cellAt:0 :j] setState:1];
  1241.       if ( [ [symbolMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
  1242.     [ [symbolMatrix cellAt:0 :j] setState:1];
  1243.       for (k=1; k<N_LINE_STYLES; k++) {
  1244.     [ [lineMatrix cellAt:k :j] setState:0];
  1245.       }
  1246.       for (k=1; k<N_SYMBOL_STYLES; k++) {
  1247.     [ [symbolMatrix cellAt:k :j] setState:0];
  1248.       }
  1249.       sprintf(formtitle, "Curve %d", j+1);
  1250.       [legendForm setStringValue:formtitle at:j];
  1251.       [legendForm drawCellAt:j];
  1252.       sprintf(formtitle, "%d", j+1);
  1253.       [[lineText cellAt:0 :j] setStringValue:formtitle]; /* titles on columns */
  1254.       [[symbolText cellAt:0 :j] setStringValue:formtitle]; /* titles on columns */
  1255.     }
  1256.     //    [legendTitle setStringValue:"Legend" at:0];  should we uncomment this?
  1257.     //                                          (would need to add an outlet in IB)
  1258.     [lineText sizeToCells];
  1259.     [symbolText sizeToCells];
  1260.     [lineMatrix sizeToCells];
  1261.     [symbolMatrix sizeToCells];
  1262.     [legendForm sizeToFit];
  1263.     [ [lineMatrix window] display];   /* redraw the whole window so extra */
  1264.     [ [symbolMatrix window] display]; /* columns get erased               */
  1265.     [ [legendForm window] display];
  1266.     [curveNumber setIntValue:1]; /* update curve no. on color panel */
  1267.     [curveColorWell setColor:NX_COLORBLACK]; /* and update the color */
  1268.     /* (the background and text color wells are not reset) */
  1269.   }
  1270.  
  1271.   if ( (oldn == 0) && (newn > 1) ) { /* can only happen first time */
  1272.     pdh = [datahunkArray elementAt:0];
  1273.     for (j=1; j<newn; j++) {
  1274.       [lineText addCol];
  1275.       [symbolText addCol];
  1276.       [lineMatrix addCol];
  1277.       if ( [ [lineMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
  1278.     [ [lineMatrix cellAt:0 :j] setState:1];
  1279.       /* highlight 1st row new column -- this occurs automatically */
  1280.       [symbolMatrix addCol];
  1281.       if ( [ [symbolMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
  1282.     [ [symbolMatrix cellAt:0 :j] setState:1];
  1283.       /* highlighting of 1st row new column occurs automatically */
  1284.       sprintf(formtitle, "Curve %d:", j+1);
  1285.       [legendForm addEntry:formtitle];
  1286.       sprintf(formtitle, "%s, Curve %d",
  1287.           strrchr(pdh->filename,'/')==NULL ? pdh->filename
  1288.                                            : strrchr(pdh->filename,'/') + 1,
  1289.           j+1);
  1290.       [legendForm setStringValue:formtitle at:j];
  1291.       [legendForm drawCellAt:j];
  1292.       sprintf(formtitle, "%d", j+1);
  1293.       [[lineText cellAt:0 :j] setStringValue:formtitle]; /* titles on columns */
  1294.       [[symbolText cellAt:0 :j] setStringValue:formtitle]; /* titles on columns */
  1295.     }
  1296.     sprintf(formtitle, "%s, Curve 1",
  1297.         strrchr(pdh->filename,'/')==NULL ? pdh->filename
  1298.                                          : strrchr(pdh->filename,'/') + 1);
  1299.     [legendForm setStringValue:formtitle at:0];
  1300.     [legendForm drawCellAt:0];
  1301.     [lineText sizeToCells];
  1302.     [symbolText sizeToCells];
  1303.     [lineMatrix sizeToCells];
  1304.     [symbolMatrix sizeToCells];
  1305.     [legendForm sizeToFit];
  1306.     [lineText display];
  1307.     [symbolText display];
  1308.     [lineMatrix display];    /* don't need to redraw the window here */
  1309.     [symbolMatrix display];
  1310.     [legendForm display];
  1311.   }
  1312.   if ( (oldn == 0) && (newn == 1) ) { /* only for legendForm update */
  1313.     pdh = [datahunkArray elementAt:0];
  1314.     sprintf(formtitle, "%s, Curve 1",
  1315.         strrchr(pdh->filename,'/')==NULL ? pdh->filename
  1316.                                          : strrchr(pdh->filename,'/') + 1);
  1317.     [legendForm setStringValue:formtitle at:0];
  1318.     [legendForm drawCellAt:0];
  1319.     [legendForm display];
  1320.   }
  1321.   if ( oldn != 0 ) {        /* must add columns */
  1322.     pdh = [datahunkArray elementAt:(nfilestotal-1)];
  1323.     for (j=oldn; j<oldn+newn; j++) {
  1324.       [lineText addCol];
  1325.       [symbolText addCol];
  1326.       [lineMatrix addCol];
  1327.       [ [lineMatrix cellAt:0 :j] setState:1];
  1328.       /* highlighting of 1st row new column occurs automatically */
  1329.       [symbolMatrix addCol];
  1330.       [ [symbolMatrix cellAt:0 :j] setState:1];
  1331.       /* highlighting of 1st row new column occurs automatically */
  1332.       sprintf(formtitle, "Curve %d:", j+1);
  1333.       [legendForm addEntry:formtitle];
  1334.       sprintf(formtitle, "%s, Curve %d",
  1335.           strrchr(pdh->filename,'/')==NULL ? pdh->filename
  1336.                                            : strrchr(pdh->filename,'/') + 1,
  1337.           j+1-oldn);
  1338.       [legendForm setStringValue:formtitle at:j];
  1339.       [legendForm drawCellAt:j];
  1340.       sprintf(formtitle, "%d", j+1);
  1341.       [[lineText cellAt:0 :j] setStringValue:formtitle];
  1342.       [[symbolText cellAt:0 :j] setStringValue:formtitle];
  1343.     }
  1344.     [lineText sizeToCells];
  1345.     [symbolText sizeToCells];
  1346.     [lineMatrix sizeToCells];
  1347.     [symbolMatrix sizeToCells];
  1348.     [legendForm sizeToFit];
  1349.     [lineText display];
  1350.     [symbolText display];
  1351.     [lineMatrix display];    /* don't need to redraw the window here */
  1352.     [symbolMatrix display];
  1353.     [legendForm display];
  1354.   }
  1355.  
  1356. // added code here by pdhowell for ScrollWindowing
  1357.   [legendForm getFrame:&legendFormFrame];
  1358.   [lineMatrix getFrame:&lineMatrixFrame];
  1359.   [symbolMatrix getFrame:&symbolMatrixFrame];
  1360. // Put MAX in the the following lines because we don't want to allow the windows
  1361. // to get so small that the sliders aren't drawn; the 282 and 288 are the original
  1362. // sizes of the lineMatrixWindow and symbolMatrixWindow -- dcj
  1363.   [[[lineMatrixWindow contentView] docView]
  1364.                                    sizeTo:MAX(128.0+lineMatrixFrame.size.width,282.0)
  1365.                                          :92+lineMatrixFrame.size.height];
  1366.   [[[symbolMatrixWindow contentView] docView]
  1367.                                   sizeTo:MAX(128.0+symbolMatrixFrame.size.width,288.0)
  1368.                                         :92+symbolMatrixFrame.size.height];
  1369.   [[[legendFormWindow contentView] docView]
  1370.                                    sizeTo:32+legendFormFrame.size.width
  1371.                                          :192+legendFormFrame.size.height];
  1372.  
  1373. // Try sending windowDidResize (the "self" is bogus but does no harm) -- dcj
  1374.   [legendFormWindow windowDidResize:self];
  1375.   [lineMatrixWindow windowDidResize:self];
  1376.   [symbolMatrixWindow windowDidResize:self];
  1377.  
  1378. // Try to get the windows to display properly ... dcj
  1379.   [ [lineMatrix window] display];
  1380.   [ [symbolMatrix window] display];
  1381.   [ [legendForm window] display];
  1382.  
  1383.   return self;
  1384. }
  1385.  
  1386. // Go through a particular datahunk and find values for datamin.x,
  1387. // datamax.x, datamin.y, datamax.y
  1388. // We ignore any curves that are "turned off" (linestyle=NONE & symbolstyle=NONE);
  1389. // if all curves in the datahunk are turned off we ignore the x-data, too.
  1390. - findMinMax:(datahunk *)pdh
  1391. {
  1392.   int i, j;
  1393.   datahunk *qdh;
  1394.   int n, ncurve;
  1395.   BOOL ignore;
  1396.  
  1397.   pdh->datamin.x = MAXFLOAT;
  1398.   pdh->datamax.x = -MAXFLOAT;
  1399.   pdh->datamin.y = MAXFLOAT;
  1400.   pdh->datamax.y = -MAXFLOAT;
  1401.  
  1402.   // Find out which curves belong to datahunk pdh
  1403.   ncurve = 0;
  1404.   for (n=0; n<nfilestotal; n++) {
  1405.     qdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  1406.     if (qdh == pdh)
  1407.       break;
  1408.     else
  1409.       ncurve += qdh->ncurves;
  1410.   }
  1411.   // Now we know to look at curves ncurve, ncurve+1,...,ncurve+pdh->ncurves-1
  1412.   ignore = YES;
  1413.   for (n = ncurve; n < ncurve + pdh->ncurves; n++) {
  1414.     if ([self providelinestyle:n] != NOLINE
  1415.     || [self providesymbolstyle:n] != NOSYMBOL)
  1416.       ignore = NO;
  1417.   }
  1418.   // Now it's possible we want to ignore all the curves.
  1419.   if (ignore) return self;
  1420.  
  1421.   // If we don't ignore all the curves then we must look at the x data.
  1422.   for (i = 0; i < pdh->npoints; i++)  {
  1423.     pdh->datamin.x = MIN(pdh->datamin.x, pdh->x[i]);
  1424.     pdh->datamax.x = MAX(pdh->datamax.x, pdh->x[i]);
  1425.   }
  1426.   for (j = 0; j < pdh->ncurves; j++) {
  1427.     if ([self providelinestyle:(ncurve+j)] != NOLINE
  1428.     || [self providesymbolstyle:(ncurve+j)] != NOSYMBOL) {
  1429.       for (i = 0; i < pdh->npoints; i++) {
  1430.     pdh->datamin.y = MIN(pdh->datamin.y, *(*(pdh->y+j)+i));
  1431.     pdh->datamax.y = MAX(pdh->datamax.y, *(*(pdh->y+j)+i));
  1432.       }
  1433.     }
  1434.   }
  1435.   return self;
  1436. }
  1437.  
  1438. - findGlobalMinMax
  1439. {
  1440.   int n;
  1441.   datahunk *pdh;
  1442.  
  1443.   globaldatamin.x = MAXFLOAT;
  1444.   globaldatamin.y = MAXFLOAT;
  1445.   globaldatamax.x = -MAXFLOAT;
  1446.   globaldatamax.y = -MAXFLOAT;
  1447.   for (n=0; n<nfilestotal; n++) {
  1448.     pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  1449.     globaldatamin.x = MIN(globaldatamin.x, pdh->datamin.x);
  1450.     globaldatamax.x = MAX(globaldatamax.x, pdh->datamax.x);
  1451.     globaldatamin.y = MIN(globaldatamin.y, pdh->datamin.y);
  1452.     globaldatamax.y = MAX(globaldatamax.y, pdh->datamax.y);
  1453.   }
  1454.   return self;
  1455. }
  1456.  
  1457. // Get pleasing values for the min, max, and increments
  1458. - niceMinMaxInc
  1459. {
  1460.   float fmin, fmax, finc;
  1461.  
  1462.   fmin = globaldatamin.x;
  1463.   fmax = globaldatamax.x;
  1464.   if ([self xaxisLog] ) {
  1465.     computeNiceLogInc(&fmin, &fmax, &finc);
  1466.   }
  1467.   else {
  1468.     computeNiceLinInc(&fmin, &fmax, &finc);
  1469.   }
  1470.   [self resetXmin:fmin];
  1471.   [self resetXmax:fmax];
  1472.   [self resetXinc:finc];
  1473.  
  1474.   fmin = globaldatamin.y;
  1475.   fmax = globaldatamax.y;
  1476.   if ([self yaxisLog] ) {
  1477.     computeNiceLogInc(&fmin, &fmax, &finc);
  1478.   }
  1479.   else {
  1480.     computeNiceLinInc(&fmin, &fmax, &finc);
  1481.   }
  1482.   [self resetYmin:fmin];
  1483.   [self resetYmax:fmax];
  1484.   [self resetYinc:finc];
  1485.   return self;
  1486. }
  1487.  
  1488. // Use the OpenPanel object to get a filename
  1489. - open:sender
  1490. {
  1491.   static const char *const fileTypes[2] = {NULL, NULL};
  1492.                 /* this is supposedly all ASCII files */
  1493.   char  fname[256];
  1494.   char  tempfname[256], command[512];
  1495.   id openPanel = [[OpenPanel new] allowMultipleFiles:NO];
  1496.  
  1497.   [openPanel setAccessoryView:nil]; /* may have to clean out an accessory view */
  1498.   if (nfilestotal == 0) {
  1499.     if (strncmp([errorBars title], "No error bars", 13) != 0) {
  1500.       [openPanel setTitle:"Open (error bars)"];
  1501.     }
  1502.     else {
  1503.       [openPanel setTitle:"Open"];        /* make sure title is OK (cf. binary open) */
  1504.     }
  1505.   }
  1506.   else {
  1507.     if (strncmp([errorBars title], "No error bars", 13) != 0) {
  1508.       [openPanel setTitle:"Another (error bars)"]; /* 21 character limit here? */
  1509.     }
  1510.     else {
  1511.       [openPanel setTitle:"Open Additional File"];
  1512.     }
  1513.   }
  1514.  
  1515.   if ([openPanel runModalForTypes:fileTypes])  {
  1516.     strncpy(fname, (char *)[openPanel filename], 256);
  1517.     // Check to see if we are trying to open a compressed file:
  1518.     if (fname[strlen(fname)-1]=='Z' && fname[strlen(fname)-2]=='.') {
  1519.       // set plot button title:
  1520.       [plotButton setAltTitle:"Uncompressing"];
  1521.       [plotButton highlight:YES];
  1522.       NXPing();            /* force plotButton redraw */
  1523.       // Uncompress the file into a temporary file:
  1524.       strcpy(tempfname, "/tmp/file000000.xyp");
  1525.       NXGetTempFilename(tempfname, 9);
  1526.       sprintf(command, "zcat %s > %s\n", fname, tempfname);
  1527.       system(command);        /* no error checking */
  1528.       // Now just go ahead and open the temporary file:
  1529.       [self openFile:tempfname :fname];
  1530.       // After returning from the openFile it is safe to unlink:
  1531.       unlink(tempfname);
  1532.     }
  1533.     else {
  1534.       [self openFile:fname :fname];
  1535.     }
  1536.   }
  1537.   return self;
  1538. }
  1539.  
  1540. - openFile:(char *)dataFile :(char *)realName
  1541. {
  1542.   NXStream *dataStream;
  1543.  
  1544.   if ((dataStream = NXMapFile(dataFile, NX_READONLY)) == NULL)  {
  1545.     NXRunAlertPanel("Open", "Cannot open %s", "OK", NULL, NULL, dataFile);
  1546.     return self;
  1547.   }
  1548.  
  1549.   if ([self readData:dataStream :realName] == 0)  {
  1550.     NXRunAlertPanel("Read", "Couldn't read any data from %s", "OK",
  1551.             NULL, NULL, dataFile);
  1552.     NXCloseMemory(dataStream, NX_FREEBUFFER);
  1553.     return self;
  1554.   }
  1555.   NXCloseMemory(dataStream, NX_FREEBUFFER);
  1556.   [self plotPrepAndDraw];
  1557.   return self;
  1558. }
  1559.  
  1560. - openBinary:sender
  1561. {
  1562.   static const char *const fileTypes[2] = {NULL, NULL};
  1563.   char  fname[256];
  1564.   id openPanel = [[OpenPanel new] allowMultipleFiles:NO];
  1565.  
  1566.   [openPanel setAccessoryView:binaryOpenAccessory];
  1567.   if (strncmp([errorBars title], "No error bars", 13) != 0) {
  1568.     [openPanel setTitle:"Open Binary (error bars)"];
  1569.   }
  1570.   else {
  1571.     [openPanel setTitle:"Open Binary File"];
  1572.   }
  1573.  
  1574.   if ([openPanel runModalForTypes:fileTypes])  {
  1575.     if ([binaryOpenForm intValueAt:0] < 1) {
  1576.       NXRunAlertPanel("Binary Read",
  1577.               "Number of curves is less than 1\n"
  1578.               "Be sure to set this correctly",
  1579.               "OK", NULL, NULL);
  1580.       return self;
  1581.     }
  1582.     strncpy(fname, (char *)[openPanel filename], 256);
  1583.     [self openBinaryFile:fname];
  1584.   }
  1585.   return self;
  1586. }
  1587.  
  1588. #import <sys/stat.h>
  1589. - openBinaryFile:(char *)dataFile
  1590. {
  1591.   struct stat filestat;
  1592.   int    filesize, numpoints;
  1593.   int    numcurves = [binaryOpenForm intValueAt:0];
  1594.   BOOL   xdatathere = [binaryXdatathere state];
  1595.   NXStream *dataStream;
  1596.   int    numcols;        /* number of "columns" expected in the data file */
  1597.  
  1598.   if (strncmp([errorBars title], "No error bars", 13) == 0) {
  1599.     numcols = numcurves;    /* no error bars */
  1600.   }
  1601.   else if (strncmp([errorBars title], "y only", 6) == 0) {
  1602.     numcols = 2*numcurves;
  1603.   }
  1604.   else if (strncmp([errorBars title], "x only", 6) == 0) {
  1605.     numcols = numcurves + 1;
  1606.   }
  1607.   else {
  1608.     numcols = 2*numcurves + 1;
  1609.   }
  1610.  
  1611.   stat(dataFile, &filestat);
  1612.   filesize = filestat.st_size;
  1613.  
  1614.   if (xdatathere) {
  1615.     // This consistency check is not sufficient to guarantee numcurves is
  1616.     // correct, but it's better than nothing.
  1617.     if ( (filesize % (sizeof(NXCoord)*(numcols+1))) != 0 ) {
  1618.       NXRunAlertPanel("Binary Read",
  1619.               "File size inconsistent with number\n"
  1620.               "of curves specified",
  1621.               "OK", NULL, NULL);
  1622.       return self;
  1623.     }
  1624.     numpoints = filesize/(sizeof(NXCoord)*(numcols+1));
  1625.   }
  1626.   else {
  1627.     if ( (filesize % (sizeof(NXCoord)*numcols)) != 0 ) {
  1628.       NXRunAlertPanel("Binary Read",
  1629.               "File size inconsistent with number\n"
  1630.               "of curves specified",
  1631.               "OK", NULL, NULL);
  1632.       return self;
  1633.     }
  1634.     numpoints = filesize/(sizeof(NXCoord)*(numcols));
  1635.   }
  1636.   
  1637.   if ((dataStream = NXMapFile(dataFile, NX_READONLY)) == NULL)  {
  1638.     NXRunAlertPanel("Open", "Cannot open %s", "OK", NULL, NULL, dataFile);
  1639.     return self;
  1640.   }
  1641.  
  1642.   if ([self readBinaryData:dataStream :dataFile :numcurves 
  1643.                           :numpoints :xdatathere] == 0)  {
  1644.     NXRunAlertPanel("Read", "Couldn't read any data from %s", "OK",
  1645.             NULL, NULL, dataFile);
  1646.     NXCloseMemory(dataStream, NX_FREEBUFFER);
  1647.     return self;
  1648.   }
  1649.   NXCloseMemory(dataStream, NX_FREEBUFFER);
  1650.   [self plotPrepAndDraw];
  1651.  
  1652.   return self;
  1653. }
  1654.  
  1655. - (int)readBinaryData:(NXStream *)aDataStream
  1656.                      :(char *)fname
  1657.                      :(int)numcurves
  1658.                      :(int)numpoints
  1659.                      :(BOOL)xdatathere
  1660. {
  1661.   int      j, oldncurves = ncurvestotal;
  1662.   datahunk *pdh = (void *)NULL;
  1663.   
  1664.   [self preludeToReading:fname :&pdh];    /* take care of some housekeeping */
  1665.   pdh->ncurves = numcurves;
  1666.  
  1667.   /* Now read the data into memory */
  1668.   NXSeek(aDataStream, 0L, NX_FROMSTART);
  1669.     
  1670.   pdh->x = (NXCoord *)malloc( numpoints * sizeof(NXCoord) );
  1671.   if (pdh->has_exbars) {
  1672.     pdh->ex = (NXCoord *)malloc( numpoints * sizeof(NXCoord *) );
  1673.   }
  1674.   pdh->y = (NXCoord **)malloc( pdh->ncurves * sizeof(NXCoord *) );
  1675.   for (j = 0; j < pdh->ncurves; j++) {
  1676.     *(pdh->y+j) = (NXCoord *)malloc( numpoints * sizeof(NXCoord) );
  1677.   }
  1678.   if (pdh->has_eybars) {
  1679.     pdh->ey = (NXCoord **)malloc( pdh->ncurves * sizeof(NXCoord *) );
  1680.     for (j = 0; j < pdh->ncurves; j++) {
  1681.       *(pdh->ey+j) = (NXCoord *)malloc( numpoints * sizeof(NXCoord) );
  1682.     }
  1683.   }
  1684.     
  1685.   pdh->npoints = numpoints;
  1686.  
  1687.   if (xdatathere) {
  1688.     NXRead(aDataStream, pdh->x, numpoints*sizeof(NXCoord));
  1689.   }
  1690.   else {
  1691.     for (j=0; j < numpoints; j++) {
  1692.       pdh->x[j] = (float)j;
  1693.     }
  1694.   }
  1695.   if (pdh->has_exbars) {
  1696.     NXRead(aDataStream, pdh->ex, numpoints*sizeof(NXCoord));
  1697.   }
  1698.   for (j = 0; j < pdh->ncurves; j++) {
  1699.     NXRead(aDataStream, *(pdh->y+j), numpoints*sizeof(NXCoord));
  1700.     if (pdh->has_eybars) {
  1701.       NXRead(aDataStream, *(pdh->ey+j), numpoints*sizeof(NXCoord));
  1702.     }
  1703.   }
  1704.  
  1705.   [self postludeToReading:fname :oldncurves :pdh];
  1706.  
  1707.   ncurvestotal += pdh->ncurves;
  1708.  
  1709.   return pdh->npoints;
  1710. }
  1711.  
  1712.  
  1713. - postludeToReading:(char *)fname :(int)oldncurves :(datahunk *)pdh
  1714. {
  1715.   int   j;
  1716.   float hue = 0.0;
  1717. #define N_PREDEFINED_COLORS 9
  1718.   NXColor predefined_colors[] = {NX_COLORRED, NX_COLORGREEN, NX_COLORBLUE,
  1719.                      NX_COLORCYAN, NX_COLORYELLOW, NX_COLORMAGENTA,
  1720.                      NX_COLORORANGE, NX_COLORPURPLE, NX_COLORBROWN};
  1721. // Those colors come from /usr/include/appkit/color.h
  1722.  
  1723.   /* Adjust the lineMatrix, symbolMatrix, and legendForm */
  1724.   [self adjustPanels:oldncurves :pdh->ncurves];
  1725.  
  1726.   if ([columnPanel isVisible])
  1727.     [columnSelectionHandler fixPanel:self];
  1728.  
  1729.   /*
  1730.    * Error bars don't get drawn unless the error bar matrix is correct.
  1731.    * Therefore we don't test on visibility here, rather we test on pdh itself.
  1732.    */
  1733.   if (pdh->has_exbars || pdh->has_eybars)
  1734.     [errorBarHandler updatePanel:self];
  1735.  
  1736.   if ([fileRemovalPanel isVisible])
  1737.     [self fixFileRemovalPanel:self];
  1738.  
  1739.   /* reset plot button */
  1740.   [plotButton setAltTitle:"Plotting"];
  1741.   [plotButton highlight:NO];
  1742.  
  1743.   [self findMinMax:pdh];
  1744.   /*
  1745.    * Don't bother to check lin/log unless this is the first file or
  1746.    * we already have at least one logarithmic axis:
  1747.    */
  1748.   if (nfilestotal==1 || [self xaxisLog] || [self yaxisLog] ) {
  1749.     [self checkLinLog:pdh];
  1750.   }
  1751.   else {
  1752.     pdh->xaxislin = YES;
  1753.     pdh->yaxislin = YES;
  1754.   }
  1755.  
  1756.   curvecolors = (NXColor *)realloc((void *)curvecolors,
  1757.                               (ncurvestotal + pdh->ncurves) * sizeof(NXColor));
  1758.  
  1759.   [[canvas window] setTitleAsFilename:fname];
  1760.  
  1761.   if (!colorOption) {
  1762.     for (j=0; j<pdh->ncurves; j++) {
  1763.       curvecolors[j + ncurvestotal] = NX_COLORBLACK;
  1764.     }
  1765.   }
  1766.   else {            /* adjust all the curve colors */
  1767. //    for (j=0; j<pdh->ncurves + ncurvestotal; j++) {
  1768. //      hue = 0.6667 * (float)((j+1) % MIN(11, ncurvestotal + pdh->ncurves))
  1769. //                       / (float)MIN(10, ncurvestotal + pdh->ncurves);
  1770. //      curvecolors[j] = NXConvertHSBToColor(hue, 1.0, 1.0);
  1771. //      /* update curve no. on color panel */
  1772. //      [curveNumber setIntValue:j+1];
  1773. //      /* and update the color */
  1774. //      [curveColorWell setColor:curvecolors[j]];
  1775. //    }
  1776. // The preceding has the unfortunate effect of changing colors on an already-plotted
  1777. // curve if a new file is read in.  This is undesirable.  Here is a more
  1778. // primitive method.  Maybe a method based on (repeatable) random number
  1779. // generation would be appropriate.
  1780. //    for (j=0; j<pdh->ncurves; j++) {
  1781. //      hue = 0.6667 * (float)((j+ncurvestotal+1) % 6) / 5.0;
  1782. //      curvecolors[j + ncurvestotal] = NXConvertHSBToColor(hue, 1.0, 1.0);
  1783. //      /* update curve no. on color panel */
  1784. //      [curveNumber setIntValue:j+ncurvestotal+1];
  1785. //      /* and update the color */
  1786. //      [curveColorWell setColor:curvecolors[j+ncurvestotal]];
  1787. //    }
  1788. // All right, here is the method based on random numbers.  Maybe this will be OK.
  1789. // The trouble with it is that with srandom(10) [see above -- this was about
  1790. // the best] the first two curves are about the same shade of green.  Now the
  1791. // strategy is to preset the first few curve colors, after that they will be
  1792. // random.
  1793.     for (j=0; j<pdh->ncurves; j++) {
  1794.       if (j + ncurvestotal < N_PREDEFINED_COLORS) {
  1795.     curvecolors[j + ncurvestotal] = predefined_colors[j + ncurvestotal];
  1796.       }
  1797.       else {
  1798.     hue = 0.8 * ((float)random())/2147483647.0 ; /* 2^31 - 1 */
  1799.     curvecolors[j + ncurvestotal] = NXConvertHSBToColor(hue, 1.0, 1.0);
  1800.       }
  1801.       /* update curve no. on color panel */
  1802.       [curveNumber setIntValue:j+ncurvestotal+1];
  1803.       /* and update the color */
  1804.       [curveColorWell setColor:curvecolors[j+ncurvestotal]];
  1805.     }
  1806.   }
  1807.  
  1808.   return self;
  1809. }
  1810.  
  1811. - preludeToReading:(char *)fname :(datahunk **)pdh
  1812. {
  1813.   datahunk dh;
  1814.  
  1815.   /* set plot button title "Reading" */
  1816.   [plotButton setAltTitle:"Reading"];
  1817.   [plotButton highlight:YES];
  1818.   NXPing();            /* force plotButton redraw */
  1819.   
  1820.   if (nfilestotal == 0) {
  1821.     datahunkArray = [Storage newCount:1
  1822.       elementSize:sizeof(datahunk)
  1823.       description:"{*{float *}{float *}{float **}{float **}iiffff{BOOL}{BOOL}{BOOL}{BOOL}}"];
  1824.     *pdh = (datahunk *)[datahunkArray elementAt:0];
  1825.     if (*pdh == NULL) {
  1826.       NXRunAlertPanel("readData",
  1827.               "Weird error 0: NULL pointer in readData\n"
  1828.               "I can't continue",
  1829.               "OK", NULL, NULL);
  1830.       exit(0);
  1831.     }
  1832.     nfilestotal = 1;
  1833.   }
  1834.   else {
  1835.     [datahunkArray addElement:(void *)&dh];
  1836.     *pdh = (datahunk *)[datahunkArray elementAt:(unsigned)nfilestotal];
  1837.     if (*pdh == NULL) {
  1838.       NXRunAlertPanel("readData",
  1839.               "Weird error 1: NULL pointer in readData\n"
  1840.               "I can't continue",
  1841.               "OK", NULL, NULL);
  1842.       exit(0);
  1843.     }
  1844.     nfilestotal++;
  1845.   }
  1846.   (*pdh)->filename = (char *)malloc(strlen(fname) + 1);
  1847.   strncpy((*pdh)->filename, fname, strlen(fname) + 1);
  1848.  
  1849.   (*pdh)->has_exbars = NO;
  1850.   (*pdh)->has_eybars = NO;
  1851.   if (strncmp([errorBars title], "y only", 6) == 0) {
  1852.     (*pdh)->has_eybars = YES;
  1853.   }
  1854.   else if (strncmp([errorBars title], "x only", 6) == 0) {
  1855.     (*pdh)->has_exbars = YES;
  1856.   }
  1857.   else if (strncmp([errorBars title], "x and y", 7) == 0) {
  1858.     (*pdh)->has_exbars = YES;
  1859.     (*pdh)->has_eybars = YES;
  1860.   }
  1861.  
  1862.   return self;
  1863. }
  1864.  
  1865. - plotPrepAndDraw
  1866. {
  1867.   /* Check for linear or log on x and y axes */
  1868.   [self checkGlobalLinLog];
  1869.  
  1870.   [self findGlobalMinMax];
  1871.  
  1872.   /* Only check and reset min/max if this is the first file */
  1873.   if (nfilestotal == 1) {
  1874.     [self niceMinMaxInc];
  1875.   }
  1876.  
  1877.   [self drawPlot:self];
  1878.     
  1879.   return self;
  1880. }
  1881.  
  1882. - writeDataFiles:sender
  1883. {
  1884.   int i, j, n;
  1885.   datahunk *pdh;
  1886.   NXStream *outputStream;
  1887.   char filename[1024], paneltitle[256];
  1888.   id    savePanel = [[SavePanel new] setRequiredFileType:""];
  1889.  
  1890.   if (nfilestotal == 0) {
  1891.     NXRunAlertPanel("Write Data", "No data", "OK", NULL, NULL);
  1892.   }
  1893.   else {
  1894.     for (n=0; n<nfilestotal; n++) {
  1895.       pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  1896.       sprintf(paneltitle, "Save file %d (%s)", n+1,
  1897.           strrchr(pdh->filename,'/')==NULL ? pdh->filename
  1898.                                            : strrchr(pdh->filename,'/')+1);
  1899.       [savePanel setTitle:paneltitle];
  1900.       [savePanel setAccessoryView:writeDataAccButton];
  1901.       if ([savePanel runModal]) {
  1902.     strncpy(filename, [savePanel filename], 1024);
  1903.     if ((outputStream = NXOpenMemory(NULL, 0, NX_WRITEONLY)) == NULL) {
  1904.       NXRunAlertPanel("Write Data", "Cannot open memory for file %d",
  1905.               "OK", NULL, NULL, n);
  1906.       return self;
  1907.     }
  1908.     if ([writeDataAccButton state]) { /* ascii write */
  1909.       for (i=0; i<pdh->npoints; i++) {
  1910.         NXPrintf(outputStream, "%g", pdh->x[i]);
  1911.         if (pdh->has_exbars) {
  1912.           NXPrintf(outputStream, " %g", pdh->ex[i]);
  1913.         }
  1914.         for (j=0; j<pdh->ncurves; j++) {
  1915.           NXPrintf(outputStream, " %g", *(*(pdh->y+j)+i));
  1916.           if (pdh->has_eybars) {
  1917.         NXPrintf(outputStream, " %g", *(*(pdh->ey+j)+i));
  1918.           }
  1919.         }
  1920.         NXPrintf(outputStream, "\n");
  1921.       }
  1922.     }
  1923.     else {            /* binary write */
  1924.       NXWrite(outputStream, pdh->x, (pdh->npoints)*sizeof(NXCoord));
  1925.       if (pdh->has_exbars) {
  1926.         NXWrite(outputStream, pdh->ex, (pdh->npoints)*sizeof(NXCoord));
  1927.       }
  1928.       for (j=0; j<pdh->ncurves; j++) {
  1929.         NXWrite(outputStream, *(pdh->y+j), (pdh->npoints)*sizeof(NXCoord));
  1930.         if (pdh->has_eybars) {
  1931.           NXWrite(outputStream, *(pdh->ey+j), (pdh->npoints)*sizeof(NXCoord));
  1932.         }
  1933.       }
  1934.     }
  1935.     NXFlush(outputStream);
  1936.     NXSaveToFile(outputStream, filename);
  1937.     NXClose(outputStream);
  1938.       }
  1939.     }
  1940.   }
  1941.   return self;
  1942. }
  1943.     
  1944. - whyTheBeep:sender
  1945. {
  1946.   switch(beepError) {
  1947.   case 0:
  1948.     NXRunAlertPanel("The Beep Panel", "Press this button if the program beeps\n"
  1949.        "and you want to know why", "OK", NULL, NULL);
  1950.     break;
  1951.   case 1:
  1952.     NXRunAlertPanel("The Beep Happened Because:",
  1953.             "x-axis changed from log to linear", "OK", NULL, NULL);
  1954.     break;
  1955.   case 2:
  1956.     NXRunAlertPanel("The Beep Happened Because:",
  1957.             "y-axis changed from log to linear", "OK", NULL, NULL);
  1958.     break;
  1959.   case 3:
  1960.     NXRunAlertPanel("The Beep Happened Because:",
  1961.             "x-increment was negative\n (I changed it)", "OK", NULL, NULL);
  1962.     break;
  1963.   case 4:
  1964.     NXRunAlertPanel("The Beep Happened Because:",
  1965.             "xmax was less than xmin\n (I changed them)", "OK", NULL, NULL);
  1966.     break;
  1967.   case 5:
  1968.     NXRunAlertPanel("The Beep Happened Because:",
  1969.             "y-increment was negative\n (I changed it)", "OK", NULL, NULL);
  1970.     break;
  1971.   case 6:
  1972.     NXRunAlertPanel("The Beep Happened Because:",
  1973.             "ymax was less than ymin\n (I changed them)", "OK", NULL, NULL);
  1974.     break;
  1975.   case 7:
  1976.     NXRunAlertPanel("The Beep Happened Because:",
  1977.             "Too many tic marks would have been on the x-axis\n"
  1978.             " (I changed the min, max, and/or increment)",
  1979.             "OK", NULL, NULL);
  1980.     break;
  1981.   case 8:
  1982.     NXRunAlertPanel("The Beep Happened Because:",
  1983.             "Too many tic marks would have been on the y-axis\n"
  1984.             " (I changed the min, max, and/or increment)",
  1985.             "OK", NULL, NULL);
  1986.     break;
  1987.   case 9:
  1988.     NXRunAlertPanel("The Beep Happened Because:",
  1989.             "x-axis was logarithmic but some datum was negative\n"
  1990.             " (I changed x-axis to linear)", "OK", NULL, NULL);
  1991.     break;
  1992.   case 10:
  1993.     NXRunAlertPanel("The Beep Happened Because:",
  1994.             "y-axis was logarithmic but some datum was negative\n"
  1995.             " (I changed y-axis to linear)", "OK", NULL, NULL);
  1996.     break;
  1997.   case 11:
  1998.     NXRunAlertPanel("The Beep Happened Because:",
  1999.             "tried to set color of a non-existent curve", "OK", NULL, NULL);
  2000.     break;
  2001.   case 12:
  2002.     NXRunAlertPanel("The Beep Happened Because:",
  2003.             "an increment was too small for a log axis\n"
  2004.             " (I reset it)", "OK", NULL, NULL);
  2005.     break;
  2006.   }
  2007.   return self;
  2008. }
  2009.  
  2010. - (NXColor) provideBackgroundColor
  2011. {
  2012.   if ( ( ([printPreview state] == 1) || (NXDrawingStatus == NX_PRINTING))
  2013.       && ([accPrintColorButton state] == 0) ) {
  2014.     return NX_COLORWHITE;
  2015.   }
  2016.   else {
  2017.     return backgroundcolor;
  2018.   }
  2019. }
  2020.  
  2021. - (NXColor) provideTextColor
  2022. {
  2023.   if ( ( ([printPreview state] == 1) || (NXDrawingStatus == NX_PRINTING))
  2024.       && ([accPrintColorButton state] == 0) ) {
  2025.     return NX_COLORBLACK;
  2026.   }
  2027.   else {
  2028.     return textcolor;
  2029.   }
  2030. }
  2031.  
  2032. - (NXColor) provideCurveColor:(int)aCurve
  2033. {
  2034.   if ( ( ([printPreview state] == 1) || (NXDrawingStatus == NX_PRINTING))
  2035.       && ([accPrintColorButton state] == 0) ) {
  2036.     return NX_COLORBLACK;
  2037.   }
  2038.   else {
  2039.     return curvecolors[aCurve];
  2040.   }
  2041. }
  2042.  
  2043. - setBackgroundColor:sender
  2044. {
  2045.   backgroundcolor = [sender color];
  2046.   return self;
  2047. }
  2048.  
  2049. - forceBackgroundColor:(NXColor) aColor
  2050. {
  2051.   backgroundcolor = aColor;
  2052.   return self;
  2053. }
  2054.  
  2055. - forceTextColor:(NXColor) aColor
  2056. {
  2057.   textcolor = aColor;
  2058.   return self;
  2059. }
  2060.  
  2061. - forceCurveColor:(int)curvenum :(NXColor)aColor
  2062. {
  2063.   if (curvenum >= ncurvestotal) {
  2064.     NXBeep();
  2065.     beepError = 11;
  2066.   }
  2067.   else {
  2068.     curvecolors[curvenum] = aColor;
  2069.   }
  2070.   return self;
  2071. }
  2072.  
  2073.  
  2074. - setTextColor:sender
  2075. {
  2076.   textcolor = [sender color];
  2077.   return self;
  2078. }
  2079.  
  2080. - setCurveColor:sender
  2081. {
  2082.   int curvenum = [curveNumber intValue];
  2083.  
  2084.   if (curvenum > ncurvestotal || curvenum < 1) {
  2085.     NXBeep();
  2086.     beepError = 11;
  2087.     return self;
  2088.   }
  2089. /* Change the color in the color well right away: */
  2090.   [sender setColor:[sender color]];
  2091.   NXPing();            /* make sure it gets displayed */
  2092.   curvecolors[curvenum-1] = [sender color];
  2093. /* Try to be helpful and increment the curvenumber */
  2094.   curvenum = (curvenum == ncurvestotal? 1 : curvenum + 1);
  2095. /* Slight delay (1/2 second) so the well doesn't change instantly.  */
  2096.   usleep((unsigned)500000);
  2097.   [curveNumber setIntValue:curvenum];
  2098. /* Tell the sender (a color well) to go on the next color: */
  2099.   [sender setColor:curvecolors[curvenum - 1]];
  2100.   return self;
  2101. }
  2102.  
  2103. /* Update the curveColorWell if the curve number is changed */
  2104. - textDidEnd:textObject endChar:(unsigned short)whyEnd
  2105. {
  2106.   int curvenum = [curveNumber intValue];
  2107.   if (curvenum > ncurvestotal || curvenum < 1) {
  2108.     NXBeep();
  2109.     beepError = 11;
  2110.     return self;
  2111.   }
  2112.   [curveColorWell setColor:curvecolors[curvenum-1]];
  2113.   NXPing();            /* make sure it gets displayed */
  2114.   return self;
  2115. }
  2116.  
  2117. /* Update the global colorOption variable. */
  2118. - colorOn:(BOOL)onOff
  2119. {
  2120.   if (onOff) {
  2121.     colorOption = YES;
  2122.     backgroundcolor = NX_COLORBLACK;
  2123.     textcolor = NX_COLORWHITE;
  2124.   }
  2125.   else {
  2126.     colorOption = NO;
  2127.     backgroundcolor = NX_COLORWHITE;
  2128.     textcolor = NX_COLORBLACK;
  2129.   }
  2130.   [textColorWell setColor:textcolor];
  2131.   [backgroundColorWell setColor:backgroundcolor];
  2132.   [curveColorWell setColor:textcolor];
  2133.  
  2134.   return self;
  2135. }
  2136.  
  2137. - fixMatrixColumn:sender    /* The sender is a matrix */
  2138. {
  2139.   int row, col, i;
  2140.  
  2141.   row = [sender selectedRow];
  2142.   col = [sender selectedCol];
  2143.   if (sender == lineMatrix) {
  2144.     [self adjustLineStyleMatrix:col :row];
  2145.     // Now, instead of redisplaying the whole matrix, just redraw the
  2146.     // cells in the affected column.  It's much faster this way.
  2147.     for (i=0; i<N_LINE_STYLES; i++)
  2148.       [lineMatrix drawCellAt:i :col];
  2149.   }
  2150.   else if (sender == symbolMatrix) {
  2151.     [self adjustSymbolTypeMatrix:col :row];
  2152.     // Comment above applies here, too.
  2153.     for (i=0; i<N_SYMBOL_STYLES; i++)
  2154.       [symbolMatrix drawCellAt:i :col];
  2155.   }
  2156.   return self;
  2157. }
  2158.  
  2159. /* change the column which is to be taken as the x data */
  2160. - swapColumns:(int)prev_col :(int)col forFileNumber:(int)i
  2161. {
  2162.   datahunk *pdh;
  2163.   float    *tmp;
  2164.  
  2165.   if (col < 0 || prev_col < 0)    /* be very cautious */
  2166.     return self;
  2167.  
  2168.   if (col==0 && prev_col==0)    /* must avoid this special case */
  2169.     return self;
  2170.  
  2171.   pdh    = (datahunk *)[datahunkArray elementAt:(unsigned)i];
  2172.   if (prev_col == 0) {        /* was first column, generic x-data */
  2173. //  tmp    = pdh->x;        /* why doesn't this order work? */
  2174. //  pdh->x = *(pdh->y + col-1);
  2175. //  *(pdh->y + col-1) = tmp;
  2176.     tmp    = *(pdh->y + col-1);
  2177.     *(pdh->y + col-1) = pdh->x;
  2178.     pdh->x = tmp;
  2179.   }
  2180.   else if (col == 0) {        /* revert back to generic x-data */
  2181.     tmp    = *(pdh->y + prev_col - 1);
  2182.     *(pdh->y + prev_col - 1) = pdh->x;
  2183.     pdh->x = tmp;
  2184.   }
  2185.   else {            /* swapping y's only */
  2186.     tmp    = *(pdh->y + prev_col - 1);
  2187.     *(pdh->y + prev_col - 1) = *(pdh->y + col - 1);
  2188.     *(pdh->y + col - 1) = tmp;
  2189.   }
  2190.  
  2191.   [self findMinMax:pdh];    /* reset these values here */
  2192.   [self findGlobalMinMax];
  2193.  
  2194.   return self;
  2195. }
  2196.  
  2197.  
  2198. /*
  2199.  * This function is called by the PlotView object during zooming.
  2200.  */
  2201. - stackOldMinMax:(float)xmin :(float)xmax :(float)ymin :(float)ymax
  2202. {
  2203.   oldMin = currentMin;        /* structure assignment */
  2204.   oldMax = currentMax;
  2205.   oldInc = currentInc;        /* have to deal with the increments, too */
  2206.   currentMin.x = xmin;
  2207.   currentMax.x = xmax;
  2208.   currentMin.y = ymin;
  2209.   currentMax.y = ymax;
  2210.   currentInc.x = [xInc floatValueAt:0];
  2211.   currentInc.y = [yInc floatValueAt:0];
  2212.  
  2213.   return self;
  2214. }
  2215.  
  2216. /*
  2217.  * This method is invoked by the "Previous View" button on the
  2218.  * control panel.
  2219.  */
  2220. - previousView:sender
  2221. {
  2222.   NXPoint tmp;
  2223.  
  2224.   [self resetXmin:(double)oldMin.x];
  2225.   [self resetXmax:(double)oldMax.x];
  2226.   [self resetXinc:(double)oldInc.x];
  2227.   [self resetYmin:(double)oldMin.y];
  2228.   [self resetYmax:(double)oldMax.y];
  2229.   [self resetYinc:(double)oldInc.y];
  2230.   tmp        = oldMin;
  2231.   oldMin     = currentMin;
  2232.   currentMin = tmp;
  2233.   tmp        = oldMax;
  2234.   oldMax     = currentMax;
  2235.   currentMax = tmp;
  2236.   tmp        = oldInc;
  2237.   oldInc     = currentInc;
  2238.   currentInc = tmp;
  2239.   [self drawPlotButton:1];    /* display "Plotting" */
  2240.   [canvas display];
  2241.   [self drawPlotButton:0];    /* display "Plot" */
  2242.  
  2243.   return self;
  2244. }
  2245.  
  2246. @end
  2247.